package com.ytplayer.player {
	import flash.display.Loader;
	import flash.display.SimpleButton;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.MouseEvent;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.net.URLVariables;
	import flash.system.Security;

	public class YoutubeVideoPlayer extends Sprite {		
		private static var _instance:YoutubeVideoPlayer;
		
		public var pauseButton:SimpleButton;
		public var playButton:SimpleButton;
		public var cueButton:SimpleButton;

		private var isWidescreen:Boolean;
		private var _player:Object;
		private var playerLoader:Loader;
		private var youtubeApiLoader:URLLoader;

		private var loadingId:String;

		private var initialHeight:Number = 480;
		private var initialWidth:Number = 360;

		private var _onPlayerReadyFunction:Function;
		private var _autoSize:Boolean = true;
		private var _cueVideos:Boolean = false;
		private var _loop:Boolean;
		private var _playlist:Array;
		private var playlistCurrentVideo:int

		private static const DEFAULT_VIDEO_ID:String = "0QRO3gKj3qw";
		private static const PLAYER_URL:String = "http://www.youtube.com/apiplayer?version=3";
		private static const SECURITY_DOMAIN:String = "http://www.youtube.com";
		private static const SECURITY_DOMAIN_2:String = "http://s.ytimg.com/";
		private static const YOUTUBE_API_PREFIX:String = "http://gdata.youtube.com/feeds/api/videos/";
		private static const YOUTUBE_API_VERSION:String = "2";
		private static const YOUTUBE_API_FORMAT:String = "5";
		private static const WIDESCREEN_ASPECT_RATIO:String = "widescreen";
		private static const QUALITY_TO_PLAYER_WIDTH:Object = {
			small: 320,
			medium: 640,
			large: 854,
			hd720: 1280
		};
		private static const STATE_ENDED:Number = 0;
		private static const STATE_PLAYING:Number = 1;
		private static const STATE_PAUSED:Number = 2;
		private static const STATE_CUED:Number = 5;

		public function YoutubeVideoPlayer() {			
			if (_instance) {
				throw new Error("YoutubeVideoPlayer() should be a singleton. Please use YoutubeVideoPlayer.getInstance() instead.");
			}
			trace("YOUTUBE PLAYER INIT");
			Security.allowDomain(SECURITY_DOMAIN);
			Security.allowDomain(SECURITY_DOMAIN_2);

			setupUi();
			setupPlayerLoader();
			setupYouTubeApiLoader();
			
			_onPlayerReadyFunction = playPlaylist;
			
			_instance = this;
		}

		/**
		 * Load youtube video by ID.<br>
		 * In example id of video http://www.youtube.com/v/Lfrn1oGdB6o is "Lfrn1oGdB6o"
		 * @param videoId Youtube video ID.
		 */
		public function loadVideoById(videoId:String, startSeconds:Number = 0, suggestedQuality:String = null):void {
			player.loadVideoById(videoId, startSeconds, suggestedQuality);
		}
		
		/**
		 * Loads the specified video's thumbnail and prepares the player to play the video. The player does not request the FLV until playVideo()
		 * or seekTo() is called. In YouTube Data API video feeds, the url attribute of the <media:content> tag contains a fully qualified 
		 * player URL when the tag's format attribute has a value of 5.
		 * 
		 * @param	mediaContentUrl The mediaContentUrl must be a fully qualified YouTube player URL in the format http://www.youtube.com/v/VIDEO_ID?version=3.
		 * @param	startSeconds startSeconds accepts a float/integer and specifies the time from which the video should
		 * start playing when playVideo() is called. If you specify startSeconds and then call seekTo(), then the player 
		 * plays from the time specified in the seekTo() call. When the video is cued and ready to play, the player will broadcast a video cued event (5).
		 * @param	suggestedQuality The optional suggestedQuality parameter specifies the suggested playback quality 
		 * for the video. Please see the definition of the setPlaybackQuality function for more information about playback quality.
		 */
		public function loadVideoByUrl(mediaContentUrl:String, startSeconds:Number = 0, suggestedQuality:String = null):void {
			player.loadVideoById(mediaContentUrl, startSeconds, suggestedQuality);
		}
				
		
		/**
		 * Plays playlist.
		 * @param	videoNumber Starting index in playlist.
		 */
		public function playPlaylist(videoNumber:int = 0):void
		{
			if(playlist){
				loadVideoById(playlist[videoNumber]);
			}
			playlistCurrentVideo = videoNumber;
		}

		private function setupUi():void {
			if(playButton)
			playButton.addEventListener(MouseEvent.CLICK, playButtonClickHandler);
			if(pauseButton)
			pauseButton.addEventListener(MouseEvent.CLICK, pauseButtonClickHandler);
		}

		private function setupPlayerLoader():void {
			playerLoader = new Loader();
			playerLoader.contentLoaderInfo.addEventListener(Event.INIT, playerLoaderInitHandler);
			playerLoader.load(new URLRequest(PLAYER_URL));
		}

		private function playerLoaderInitHandler(event :Event):void {
			addChild(playerLoader);
			playerLoader.content.addEventListener("onReady", onPlayerReady);
			playerLoader.content.addEventListener("onError", onPlayerError);
			playerLoader.content.addEventListener("onStateChange", onPlayerStateChange);
			playerLoader.content.addEventListener("onPlaybackQualityChange", onVideoPlaybackQualityChange);
			
			dispatchEvent(event);
		}

		private function setupYouTubeApiLoader():void {
			youtubeApiLoader = new URLLoader();
			youtubeApiLoader.addEventListener(IOErrorEvent.IO_ERROR, youtubeApiLoaderErrorHandler);
			youtubeApiLoader.addEventListener(Event.COMPLETE, youtubeApiLoaderCompleteHandler);
		}

		private function youtubeApiLoaderCompleteHandler(event:Event):void {
			var atomData:String = youtubeApiLoader.data;

			// Parse the YouTube API XML response and get the value of the aspectRatio element.
			var atomXml:XML = new XML(atomData);
			var aspectRatios:XMLList = atomXml..*::aspectRatio;

			isWidescreen = aspectRatios.toString() == WIDESCREEN_ASPECT_RATIO;

			// Cue up the video once we know whether it's widescreen.
			// Alternatively, you could start playing instead of cueing with
			if(_cueVideos){
				player.cueVideoById(loadingId);
			}else{
				player.loadVideoById(loadingId);
			}
		}

		private function playButtonClickHandler(event:MouseEvent):void {
			player.playVideo();
		}

		private function pauseButtonClickHandler(event:MouseEvent):void {
			player.pauseVideo();
		}

		private function youtubeApiLoaderErrorHandler(event:IOErrorEvent):void {
			trace("Error making YouTube API request:", event);
		}

		private function onPlayerReady(event:Event):void {
			_player = playerLoader.content;
			player.visible = false;
			
			_onPlayerReadyFunction();
		}

		private function onPlayerError(event:Event):void {
			trace("Player error:", Object(event).data);
		}

		private function onPlayerStateChange(event:Event):void {
			trace("State is", Object(event).data);

			switch (Object(event).data) {
				case STATE_ENDED:
					if(playButton) playButton.enabled = true;
					if(pauseButton) pauseButton.enabled = false;

					if (playlist) {
						playlistCurrentVideo = (playlistCurrentVideo + 1) % playlist.length;
						loadVideoById(playlist[playlistCurrentVideo]);
				}

				if (loop) player.playVideo();
				break;

			case STATE_PLAYING:
				if(playButton) playButton.enabled = false;
				if(pauseButton) pauseButton.enabled = true;

				resizePlayer();
			break;

			case STATE_PAUSED:
				if(playButton) playButton.enabled = true;
				if(pauseButton) pauseButton.enabled = false;
			break;

			case STATE_CUED:
				if(playButton) playButton.enabled = true;
				if(pauseButton) pauseButton.enabled = false;

				resizePlayer();
			break;
		}
	}

	private function onVideoPlaybackQualityChange(event:Event):void {
		trace("Current video quality:", Object(event).data);
		resizePlayer();
	}

	private function resizePlayer():void {
		if(!_autoSize){
			player.setSize(initialWidth, initialHeight);
			player.visible = true;
		}else{
			var newWidth:Number = QUALITY_TO_PLAYER_WIDTH[player.getPlaybackQuality()] || 640;
			var newHeight:Number;

			if (isWidescreen) {
				// Widescreen videos (usually) fit into a 16:9 player.
				newHeight = newWidth * 9 / 16;
			} else {
				// Non-widescreen videos fit into a 4:3 player.
				newHeight = newWidth * 3 / 4;
			}

			trace("isWidescreen is", isWidescreen, ". Size:", newWidth, newHeight);
			player.setSize(newWidth, newHeight);

			// Center the resized player on the stage.
			player.x = (stage.stageWidth - newWidth) / 2;
			player.y = (stage.stageHeight - newHeight) / 2;

			player.visible = true;
		}
	}

	/**
	 * Set up players size. <br>
	 * Property autoSize is set to false.
	 * @param	_width Requested width
	 * @param	_height Requested height
	 */
	public function setSize(_width:Number, _height:Number):void
	{
		initialWidth = _width;
		initialHeight = _height;

		_autoSize = false;
		if(player)
		player.setSize(initialWidth, initialHeight);
	}

	/**
	 * If true, size is calculated automatically from Youtube XML quality settings. <br>
	 * otherwise return false and size is set with setSize function
	 * @return true or false
	 */
	public function get autoSize():Boolean
	{
		return _autoSize;
	}

	/**
	 * If true, size is calculated automatically from Youtube XML quality settings. <br>
	 * otherwise return false and size is set with setSize function
	 * @param value true or false
	 */
	public function set autoSize(value:Boolean):void
	{
		_autoSize = value;
	}

	/**
	 * true - Loads the specified video's thumbnail and prepares the player to play the video. The player does not request the FLV until playVideo() or seekTo() is called.
	 * false - Loads and plays the specified video imideately
	 */
	public function get cueVideos():Boolean
	{
		return _cueVideos;
	}

	/**
	 * true - Loads the specified video's thumbnail and prepares the player to play the video. The player does not request the FLV until playVideo() or seekTo() is called.
	 * false - Loads and plays the specified video imideately
	 */
	public function set cueVideos(value:Boolean):void
	{
		_cueVideos = value;
	}

	/**
	 * Provides Youtube AS3 API.
	 * ie.:
	 * player.playVideo();
	 * player.pauseVideo();
	 * player.stopVideo();
	 * and more...
	 *
	 * @see http://code.google.com/intl/cs/apis/youtube/flash_api_reference.html#Operations
	 * @return Youtube AS3 player.
	 **/
	public function get player():Object
	{
		return _player;
	}

	/**
	 * @return array with youtube video's IDs
	 */
	public function get playlist():Array
	{
		return _playlist;
	}

	/**
	 * @param youtubeIDs array of youtube video's IDs <br>
	 * example: youtubePlayer.playlist = ["123456789","987654321"]; <br>
	 * videos are played in same order you see
	 */
	public function set playlist(youtubeIDs:Array):void
	{
		_playlist = youtubeIDs;
		playlistCurrentVideo = 0;
	}

	public function get loop():Boolean
	{
		return _loop;
	}

	public function set loop(value:Boolean):void
	{
		_loop = value;
	}
	
	/**
	 * Function called if player is ready - is downloaded from youtube and inialized. <br>
	 * Default function is playPlaylist();
	 */
	public function get onPlayerReadyFunction():Function 
	{
		return _onPlayerReadyFunction;
	}
	
	/**
	 * Function called if player is ready - is downloaded from youtube and inialized. <br>
	 * Default function is playPlaylist();
	 */
	public function set onPlayerReadyFunction(_function:Function):void 
	{
		_onPlayerReadyFunction = _function;
	}
	
	public static function getInstance():YoutubeVideoPlayer 
	{
		return _instance;
	}
}
}

